const fs = require('fs')
const path = require('path')
const express = require('express')
const multer = require('multer')
const app = express()
const storage = multer.diskStorage({
    destination: 'db/',
    filename: function(req, file, cb) {
      const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
      const fileExtension = path.extname(file.originalname);
      cb(null, file.fieldname + '-' + uniqueSuffix + fileExtension);
    }
});
const upload = multer({ storage: storage })
console.clear()

class FileDBError extends Error {
  constructor(message) {
    super(message)
    this.name = "FileDBError"
  }
}

const config = require("./config")

let database = []

app.get('/',(req,res)=>{
  res.send("FileDB started successfully.")
})

app.post('/api/configure',async (req,res,next)=>{
  try {
    req.on('data',async data => { 
        data = checkData(data)
        switch (data.opName) {
          case 'unloadDatabaseByUsername':
            for(let i=database.length-1;i>=0;i--){
              if(database[i].user==data.userName){
                database = arrayDeleteElem(database,i)
              }
            }
            break;
          case 'unloadDatabaseByDatabasename':
            for(let i=database.length-1;i>=0;i--){
              if(database[i].name==data.databaseName){
                database = arrayDeleteElem(database,i)
              }
            }
            break;
          case 'tableAddCol':
            await tableAddCol(data)
            break
          case 'tableChangeColType':
            await tableChangeColType(data)
            break
          case 'deleteCol':
            await deleteCol(data)
            break
          case 'moveCol':
            await moveCol(data)
            break
          default:
            res.send({
              status:0,
              content:'Operation not found'
            })
            return
        }
        res.send({
          status:1
        })
        return
    })
  } catch (error) {
    res.send({
      status:0,
      content:'Internal server error'
    })
  }
})

app.post('/api/connect',async (req,res,next)=>{
    try {
        req.on('data',async data => { 
            data = JSON.parse(checkData(data).body)
            if(data==null){
                res.send({
                    status:0,
                    content:'Invalid data format'
                })
                return
            }
            let re1 = await checkUserExists(data)
            if(re1!="Success"){
                res.send({
                    status:0,
                    content:re1
                })
                return
            }
            let re2 = await checkDatabaseExists(data)
            if(re2!="Success"){
                res.send({
                    status:0,
                    content:re2
                })
                return
            }
            let token = getToken(data)
            await prepareUserDatabase(data)
            res.send({
              status:1,
              token:token
            })
        })
    } catch (error) {
        res.send({
            status:0,
            content:'Internal server error'
        })
    }
})

app.post('/api/statement', upload.single('file'), async (req, res, next) => {
    if (!req.file) {
      res.send({
          status:0,
          content:'Upload failed'
      })
    } else {
      try {
        const file = req.file
        let fileContent = fs.readFileSync(file.path, 'utf-8')
        fileContent = JSON.parse(fileContent)
        let ctre = await checkTokenAndGetDatabaseIndex(fileContent.config)
        if(typeof(ctre)=='string'){
          res.send({
            status:0,
            content:ctre
          })
          return
        }
        fileContent['databaseIndex'] = ctre
        if(fileContent.method=='join'){
          for(let i=0;i<fileContent.tables.length;i++){
            if(database[ctre].tables[fileContent.tables[i]]==undefined){
              res.send({
                status:0,
                content:'The table \'' + fileContent.tables[i] + '\' does not exist.'
              })
              return
            }
          }
        }else{
          if(database[ctre].tables[fileContent.table]==undefined){
            res.send({
              status:0,
              content:'The table does not exist.'
            })
            return
          }
        }
        let re = await executeStatement(fileContent)
        if(re.status==false){
          res.send({
            status:0,
            content:re.content
          })
          return
        }
        res.send({
          status:1,
          content:'Success',
          results:re.results
        })
      } catch (error) {
        console.log(error)
        res.send({
          status:0,
          content:'Internal server error'
        })
      } finally {
        fs.unlinkSync(req.file.path);
      }
    }
})

async function executeStatement(data){
  updateDatabaseStatus(data.config)
  switch (data.method) {
    case 'insert':
      return await esInsert(data)
    case 'select':
      return await esSelect(data)
    case 'delete':
      return await esDelete(data)
    case 'update':
      return await esUpdate(data)
    case 'join':
      return await esJoin(data)
  }
}

async function esJoin(data){
  try {
    let re = JSON.parse(await readFile("./db/" + data.config.user + "/" + data.config.database + "/tableConfig"))
    let tablesColumns = {}
    for(let i=0;i<data.tables.length;i++){
      for(let i1=0;i1<re.length;i1++){
        if(re[i1].name==data.tables[i]){
          tablesColumns[data.tables[i]] = re[i1].columns
          break
        }
      }
    }
    //检查columns
    if(data.columns!=undefined){
      for(let i=0;i<data.columns.length;i++){
        if(tablesColumns[data.columns[i].split('.')[0]][data.columns[i].split('.')[1]]==undefined){
          throw new FileDBError('Column \"' + data.columns[i].split('.')[1] + '\" does not exist in table \"' + data.columns[i].split('.')[0] + '\".')
        }
      }
    }
    //检查conditions
    if(data.conditions!=undefined){
      for(let i=0;i<Object.keys(data.conditions).length;i++){
        if(tablesColumns[Object.keys(data.conditions)[i].split('.')[0]][Object.keys(data.conditions)[i].split('.')[1]]==undefined){
          throw new FileDBError('Column \"' + Object.keys(data.conditions)[i].split('.')[1] + '\" does not exist in table \"' + Object.keys(data.conditions)[i].split('.')[0] + '\".')
        }
        if(data.conditions[Object.keys(data.conditions)[i]].slice(0,1)=='~'){
          if(tablesColumns[data.conditions[Object.keys(data.conditions)[i]].slice(1).split('.')[0]][data.conditions[Object.keys(data.conditions)[i]].slice(1).split('.')[1]]==undefined){
            throw new FileDBError('Column \"' + data.conditions[Object.keys(data.conditions)[i]].slice(1).split('.')[1] + '\" does not exist in table \"' + data.conditions[Object.keys(data.conditions)[i]].slice(1).split('.')[0] + '\".')
          }
        }
      }
    }
    //检查sortBy
    if(data.sortBy!=undefined){
      for(let i=0;i<data.sortBy.length;i++){
        if(tablesColumns[data.sortBy[i].split('.')[0]][data.sortBy[i].split('.')[1]]==undefined){
          throw new FileDBError('Column \"' + data.sortBy[i].split('.')[1] + '\" does not exist in table \"' + data.sortBy[i].split('.')[0] + '\".')
        }
      }
    }
    //检查groupBy
    if(data.groupBy!=undefined){
      for(let i=0;i<data.groupBy.length;i++){
        if(tablesColumns[data.groupBy[i].split('.')[0]][data.groupBy[i].split('.')[1]]==undefined){
          throw new FileDBError('Column \"' + data.groupBy[i].split('.')[1] + '\" does not exist in table \"' + data.groupBy[i].split('.')[0] + '\".')
        }
      }
    }
    for(let i=0;i<database.length;i++){
      if(database[i].name==data.config.database){
        data.databaseIndex = i
        break
      }
    }
    let titles = await joinGetTitles(data)
    let ree = await joinCartesianProduct(data.databaseIndex,data.tables,0,[],titles,data.conditions,data.columns)
    let reej = []
    for(let i=0;i<ree.length;i++){
      let creej = {}
      for(let i1=0;i1<titles.length;i1++){
        creej[titles[i1]] = ree[i][i1]
        if(tablesColumns[titles[i1].split('.')[0]][titles[i1].split('.')[1]]=='number'){
          creej[titles[i1]] = JSON.parse(creej[titles[i1]])
        }
      }
      reej.push(creej)
    }
    if(data.sortBy!=undefined){
      for(let i=0;i<data.sortBy.length;i++){
        reej = jsonQuickSort(reej,data.sortBy[i])
      }
    }
    let rest2 = []
    if(data.pageSize!=undefined){
      for(let i=(data.page-1) * data.size;i<data.page*data.size && i<reej.length;i++){
        rest2.push(reej[i])
      }
    }else{
      rest2 = reej
    }
    if(data.groupBy!=undefined){
      rest2 = grouping(rest2,data.groupBy,0)
    }
    rest2 = changeJSONToArray(rest2,data.columns)
    let rere = [ data.columns==undefined?titles:data.columns ]
    for(let i=0;i<rest2.length;i++){
      rere.push(rest2[i])
    }
    return {
      status:true,
      method:'join',
      results:rere
    }
  } catch (error) {
    console.log(error)
    return {
      method:'join',
      status:false,
      content:error.toString().slice(error.toString().indexOf(':')+2)
    }
  }
}

function changeJSONToArray(arr,columns){
  if(Array.isArray(arr)){
    for(let i=0;i<arr.length;i++){
      arr[i] = changeJSONToArray(arr[i],columns)
    }
    return arr
  }else{
    if(columns==undefined){
      return Object.values(arr)
    }
    let re = []
    for(let i=0;i<columns.length;i++){
      re.push(arr[columns[i]])
    }
    return re
  }
}

async function esUpdate(data){
  try {
    let tableColumns = await getTableColumns(data)
    let tableColumnsWithoutMark = JSON.parse(JSON.stringify(tableColumns))
    delete tableColumnsWithoutMark.mark
    for(let i=0;i<Object.keys(data.set).length;i++){
      let flag = true
      for(let i1=0;i1<Object.keys(tableColumns).length;i1++){
        if(Object.keys(tableColumns)[i1]==Object.keys(data.set)[i]){
          flag = false
          break
        }
      }
      if(flag){
        throw new FileDBError("In the \"set\". The column '" + Object.keys(data.set)[i] + "' does not exist in the table.")
      }
    }
    for(let i=0;i<Object.keys(data.set).length;i++){
      if(tableColumns[Object.keys(data.set)[i]]!=typeof(data.set[Object.keys(data.set)[i]])){
        throw new FileDBError("The values in column " + Object.keys(data.set)[i] + " of the table should be of " + tableColumns[Object.keys(data.set)[i]] + " type.")
      }
    }
    if(data.conditions!=undefined){
      for(let i=0;i<Object.keys(data.conditions).length;i++){
        let flag = true
        for(let i1=0;i1<Object.keys(tableColumns).length;i1++){
          if(Object.keys(tableColumns)[i1]==Object.keys(data.conditions)[i]){
            flag = false
            break
          }
        }
        if(flag){
          throw new FileDBError("In the \"conditions\". The column '" + Object.keys(data.conditions)[i] + "' does not exist in the table.")
        }
      }
    }
    let ctable = await getTableJSONData(data.databaseIndex,data.table,tableColumnsWithoutMark)
    for(let i=0;i<ctable.length;i++){
      if(checkItem(ctable[i],data.conditions)){
        for(let i1=0;i1<Object.keys(data.set).length;i1++){
          ctable[i][Object.keys(data.set)[i1]] = data.set[Object.keys(data.set)[i1]]
        }
      }
    }
    ctable = quickSortForJSONWithBase64StringMark(ctable)
    ctable = changeDatabaseTableToSaveableFormat(ctable,tableColumnsWithoutMark)
    saveDataBaseTable(data,ctable)
    enableSaveTableSwitch(data)
    return {
      status:true,
      method:'delete'
    }
  } catch (error) {
    console.log(error)
    return {
      method:'delete',
      status:false,
      content:error.toString().slice(error.toString().indexOf(':')+2,1000)
    }
  }
}

async function esDelete(data){
  try {
    let tableColumns = await getTableColumns(data)
    let tableColumnsWithoutMark = JSON.parse(JSON.stringify(tableColumns))
    delete tableColumnsWithoutMark.mark
    if(data.conditions!=undefined){
      for(let i=0;i<Object.keys(data.conditions).length;i++){
        let flag = true
        for(let i1=0;i1<Object.keys(tableColumns).length;i1++){
          if(Object.keys(tableColumns)[i1]==Object.keys(data.conditions)[i]){
            flag = false
            break
          }
        }
        if(flag){
          throw new FileDBError("In the \"conditions\". The column '" + Object.keys(data.conditions)[i] + "' does not exist in the table.")
        }
      }
    }
    let ctable = await getTableJSONData(data.databaseIndex,data.table,tableColumnsWithoutMark)
    if(data.conditions==undefined){
      ctable = []
    }else{
      let rtable = []
      for(let i=0;i<ctable.length;i++){
        if(checkItem(ctable[i],data.conditions)==false){
          rtable.push(ctable[i])
        }
      }
      ctable = rtable
    }
    ctable = quickSortForJSONWithBase64StringMark(ctable)
    ctable = changeDatabaseTableToSaveableFormat(ctable,tableColumnsWithoutMark)
    saveDataBaseTable(data,ctable)
    enableSaveTableSwitch(data)
    return {
      status:true,
      method:'delete'
    }
  } catch (error) {
    console.log(error)
    return {
      method:'delete',
      status:false,
      content:error.toString().slice(error.toString().indexOf(':')+2,1000)
    }
  }
}

async function esSelect(data){
  try {
    let tableColumns = await getTableColumns(data)
    let tableColumnsWithoutMark = JSON.parse(JSON.stringify(tableColumns))
    delete tableColumnsWithoutMark.mark
    if(data.columns!=undefined){
      for(let i=0;i<data.columns.length;i++){
        if(tableColumns[data.columns[i]]==undefined){
          throw new FileDBError("In the \"columns\". The column '" + data.columns[i] + "' does not exist in the table.")
        }
      }
    }
    if(data.conditions!=undefined){
      for(let i=0;i<Object.keys(data.conditions).length;i++){
        let flag = true
        for(let i1=0;i1<Object.keys(tableColumns).length;i1++){
          if(Object.keys(tableColumns)[i1]==Object.keys(data.conditions)[i]){
            flag = false
            break
          }
        }
        if(flag){
          throw new FileDBError("In the \"conditions\". The column '" + Object.keys(data.conditions)[i] + "' does not exist in the table.")
        }
      }
    }
    if(data.sortBy!=undefined){
      for(let i=0;i<data.sortBy.length;i++){
        let flag = true
        for(let i1=0;i1<Object.keys(tableColumns).length;i1++){
          if(Object.keys(tableColumns)[i1]==data.sortBy[i]){
            flag = false
            break
          }
        }
        if(flag){
          throw new FileDBError("In the \"sortBy\". The column '" + data.sortBy[i] + "' does not exist in the table.")
        }
      }
    }
    let ctable = await getTableJSONData(data.databaseIndex,data.table,tableColumnsWithoutMark)
    if(typeof(data.pageSize)=='number'){
      data.page = data.pageSize
      data.size = 10
    }
    if(Array.isArray(data.pageSize)){
      data.page = data.pageSize[0]
      data.size = data.pageSize[1]
    }
    if(data.groupBy!=undefined){
      for(let i=0;i<data.groupBy.length;i++){
        let flag = true
        for(let i1=0;i1<Object.keys(tableColumns).length;i1++){
          if(Object.keys(tableColumns)[i1]==data.groupBy[i]){
            flag = false
            break
          }
        }
        if(flag){
          throw new FileDBError("In the \"groupBy\". The column '" + data.groupBy[i] + "' does not exist in the table.")
        }
      }
    }
    let rest = []
    if(data.conditions!=undefined){
      for(let i=0;i<ctable.length;i++){
        if(checkItem(ctable[i],data.conditions)){
          rest.push(ctable[i])
        }
      }
    }else{
      rest = ctable
    }
    if(data.sortBy!=undefined){
      for(let i=0;i<data.sortBy.length;i++){
        rest = jsonQuickSort(rest,data.sortBy[i])
      }
    }
    let rest2 = []
    if(data.pageSize!=undefined){
      for(let i=(data.page-1) * data.size;i<data.page*data.size && i<rest.length;i++){
        rest2.push(rest[i])
      }
    }else{
      rest2 = rest
    }
    if(data.groupBy!=undefined){
      rest2 = grouping(rest2,data.groupBy,0)
    }
    rest2 = generateResponseData(rest2,data.columns,tableColumns)
    let rest3 = [(data.columns==undefined)?(Object.keys(tableColumns)):data.columns]
    for(let i=0;i<rest2.length;i++){
      rest3.push(rest2[i])
    }
    return {
      status:true,
      method:'select',
      results:rest3
    }
  } catch (error) {
    console.log(error)
    return {
      method:'select',
      status:false,
      content:error.toString().slice(error.toString().indexOf(':')+2,1000)
    }
  }
}

async function joinGetTitles(data){
  let res = []
  let re = JSON.parse(await readFile("./db/" + data.config.user + "/" + data.config.database + "/tableConfig"))
  for(let i=0;i<data.tables.length;i++){
    for(let i1=0;i1<re.length;i1++){
      if(re[i1].name==data.tables[i]){
        for(let i2=0;i2<Object.keys(re[i1].columns).length;i2++){
          res.push(re[i1].name + "." + Object.keys(re[i1].columns)[i2])
        }
        break
      }
    }
  }
  return res
}

async function joinCartesianProduct(databaseIndex,tables,currentTableIndex,baseArray,titles,conditions,columns){
  let ctn = tables[currentTableIndex]
  let ctd = JSON.parse(JSON.stringify(getTable(databaseIndex,ctn)))
  let flag = (currentTableIndex==(tables.length-1))
  let re = []
  for(let i=0;i<ctd.length;i++){
    let tba = JSON.parse(JSON.stringify(baseArray))
    for(let i1=0;i1<ctd[i].length;i1++){
      tba.push(ctd[i][i1])
    }
    if(flag==false){
      tba = await joinCartesianProduct(databaseIndex,tables,currentTableIndex+1,tba,titles,conditions,columns)
      for(let i1=0;i1<tba.length;i1++){
        re.push(tba[i1])
      }
    }else{
      let re1 = joinCheckItem(tba,titles,conditions,columns)
      if(re1!=false){
        re.push(re1)
      }
    }
  }
  return re
}

function getArrayByArrayColumns(itemArray,columns,titles){
  if(columns==undefined){
    return itemArray
  }else{
    let re0 = []
    for(let i=0;i<columns.length;i++){
      for(let i1=0;i1<titles.length;i1++){
        if(columns[i]==titles[i1]){
          re0.push(itemArray[i1])
        }
      }
    }
    return re0
  }
}

function joinCheckItem(itemArray,titles,conditions,columns){
  if(conditions!=undefined){
    let ck = {}
    for(let i=0;i<itemArray.length;i++){
      ck[titles[i]] = itemArray[i].toString()
    }
    for(let i=0;i<Object.keys(conditions).length;i++){
      let ct = conditions[Object.keys(conditions)[i]]
      let cts = ct.slice(1)
      switch (ct.slice(0,1)) {
        case '%':
          if(ck[Object.keys(conditions)[i]].indexOf(cts)==-1){
            return false
          }
          break;
        case '=':
          if(ck[Object.keys(conditions)[i]]!=cts){
            return false
          }
          break;
        case '~':
          if(ck[Object.keys(conditions)[i]]!=ck[cts]){
            return false
          }
          break;
      }
    }
  }
  return itemArray
}

function generateResponseData(data,selectColumns,columns){
  for(let i=0;i<data.length;i++){
    if(Array.isArray(data[i])){
      data[i] = generateResponseData(data[i],selectColumns,columns)
    }else{
      data[i] = getArrFormJSONBySelectColumns(data[i],selectColumns,columns)
    }
  }
  return data
}

function getArrFormJSONBySelectColumns(json,selectColumns,columns){
  let re = []
  if(selectColumns==undefined){
    for(let i=0;i<Object.keys(columns).length;i++){
      re.push(json[Object.keys(columns)[i]])
    }
  }else{
    for(let i=0;i<selectColumns.length;i++){
      re.push(json[selectColumns[i]])
    }
  }
  return re
}

function grouping(arr,groupBy,cgb){
  let t = []
  for(let i=0;i<arr.length;i++){
    let flag = true
    for(let i1=0;i1<t.length;i1++){
      if(t[i1]==arr[i][groupBy[cgb]]){
        flag = false
        break
      }
    }
    if(flag){
      t.push(arr[i][groupBy[cgb]])
    }
  }
  let re = []
  for(let i=0;i<t.length;i++){
    let cre = []
    for(let i1=0;i1<arr.length;i1++){
      if(arr[i1][groupBy[cgb]]==t[i]){
        cre.push(arr[i1])
      }
    }
    re.push(cre)
  }
  if(cgb<groupBy.length-1){
    for(let i=0;i<re.length;i++){
      re[i] = grouping(re[i],groupBy,cgb+1)
    }
  }
  return JSON.parse(JSON.stringify(re))
}

function checkItem(item0,conditions){
  let item = JSON.parse(JSON.stringify(item0))
  for(let i=0;i<Object.keys(conditions).length;i++){
    let ccs = conditions[Object.keys(conditions)[i]].slice(1)
    item[Object.keys(conditions)[i]] = item[Object.keys(conditions)[i]].toString()
    if(conditions[Object.keys(conditions)[i]].slice(0,1)=='%'){
      if(item[Object.keys(conditions)[i]].indexOf(ccs)==-1){
        return false
      }
    }else{
      if(item[Object.keys(conditions)[i]]!=ccs){
        return false
      }
    }
  }
  return true
}

async function esInsert(data){
  try {
    let columns = JSON.parse(await readFile("./db/" + data.config.user + "/" + data.config.database + "/tableConfig"))
    for(let i=0;i<columns.length;i++){
      if(columns[i].name==data.table){
        columns = columns[i].columns
        break
      }
    }
    delete columns.mark
    data.values = checkAndModifyObject(data.values,columns)
    let ctable = quickSortForJSONWithBase64StringMark(getTableJSONData(data.databaseIndex,data.table,columns))
    let cmark = new Base64String(0)
    let total = data.values.length
    let count = 0
    while(total){
      cmark.add1()
      if(ctable[count] && cmark.get()==ctable[count].mark){
        count+=1
        continue
      }
      data.values[data.values.length - total].mark = cmark.get()
      total-=1
    }
    for(let i=0;i<data.values.length;i++){
      ctable.push(data.values[i])
    }
    ctable = quickSortForJSONWithBase64StringMark(ctable)
    ctable = changeDatabaseTableToSaveableFormat(ctable,columns)
    saveDataBaseTable(data,ctable)
    enableSaveTableSwitch(data)
    return {
      method:'insert',
      status:true
    }
  } catch (error) {
    return {
      method:'insert',
      status:false,
      content:error.toString().slice(error.toString().indexOf(':')+2,1000)
    }
  }
}

async function check(){
  if((await checkPathExists("./db"))==false){
    // await createFolder("db")
    console.log("Please run \"Configure.cmd\" to initialize the FileDBServer.")
    await waitSeconds(10)
    return
  }
  if((await checkPathExists("./db/userConfig"))==false){
    // await createFileWithContent("./db/userConfig",JSON.stringify([]))
    console.log("Please run \"Configure.cmd\" to initialize the FileDBServer.")
    await waitSeconds(10)
    return
  }
  app.listen(config.port,async () => {
    console.log(`FileDB started successfully on port ${config.port}`)
    setInterval(()=>{
      // console.log(database)
      try {
        for(let i=0;i<database.length;i++){
          for(let i1=0;i1<Object.keys(database[i].saveTableKeys).length;i1++){
            if(database[i].saveTableKeys[Object.keys(database[i].saveTableKeys)[i1]]==true){
              saveTableToFile(i,Object.keys(database[i].saveTableKeys)[i1],database[i].user,database[i].name)
            }
          }
        }
        let ct = new Date
        for(let i=0;i<database.length;i++){
            if((Math.abs(ct - database[i].time)/1000)>600){
                database = arrayDeleteElem(database,i)
            }
        }
      } catch (error) {
        
      }
      
    },1000)
  })
}

check()

async function moveCol(data){
  await prepareUserDatabase(data)
  for(let i=0;i<database.length;i++){
    if(database[i].name==data.database){
      data.databaseIndex = i
      break
    }
  }
  let columns = JSON.parse(await readFile("./db/" + data.user + "/" + data.database + "/tableConfig"))
  for(let i=0;i<columns.length;i++){
    if(columns[i].name==data.table){
      columns = columns[i].columns
      break
    }
  }
  let ckeys = Object.keys(columns)
  let ind = 0
  for(let i=0;i<ckeys.length;i++){
    if(ckeys[i]==data.colName){
      ind = i
      if(data.op=='up'){
        let t = ckeys[i-1]
        ckeys[i-1] = ckeys[i]
        ckeys[i] = t
      }else{
        let t = ckeys[i+1]
        ckeys[i+1] = ckeys[i]
        ckeys[i] = t
        i+=1
      }
    }
  }
  let cc = {}
  for(let i=0;i<ckeys.length;i++){
    cc[ckeys[i]] = columns[ckeys[i]]
  }
  let ctable = JSON.parse(JSON.stringify(getTable(data.databaseIndex,data.table)))
  for(let i=0;i<ctable.length;i++){
    for(let i1=0;i1<ctable[i].length;i1++){
      ctable[i][i1] = ctable[i][i1].toString()
    }
    for(let i1=0;i1<ctable[i].length;i1++){
      if(i1==ind){
        if(data.op=='up'){
          let t = ctable[i][i1-1]
          ctable[i][i1-1] = ctable[i][i1]
          ctable[i][i1] = t
        }else{
          let t = ctable[i][i1+1]
          ctable[i][i1+1] = ctable[i][i1]
          ctable[i][i1] = t
          i1+=1
        }
      }
    }
  }
  saveDataBaseTable(data,ctable)
  enableSaveTableSwitch(data)
}

async function deleteCol(data){
  await prepareUserDatabase(data)
  for(let i=0;i<database.length;i++){
    if(database[i].name==data.database){
      data.databaseIndex = i
      break
    }
  }
  let columns = JSON.parse(await readFile("./db/" + data.user + "/" + data.database + "/tableConfig"))
  for(let i=0;i<columns.length;i++){
    if(columns[i].name==data.table){
      columns = columns[i].columns
      break
    }
  }
  delete columns.mark
  let ctable = quickSortForJSONWithBase64StringMark(getTableJSONData(data.databaseIndex,data.table,columns))
  delete columns[data.colName]
  for(let i=0;i<ctable.length;i++){
    delete ctable[i][data.colName]
  }
  ctable = changeDatabaseTableToSaveableFormat(ctable,columns)
  saveDataBaseTable(data,ctable)
  enableSaveTableSwitch(data)
}

async function tableChangeColType(data){
  await prepareUserDatabase(data)
  for(let i=0;i<database.length;i++){
    if(database[i].name==data.database){
      data.databaseIndex = i
      break
    }
  }
  let columns = JSON.parse(await readFile("./db/" + data.user + "/" + data.database + "/tableConfig"))
  for(let i=0;i<columns.length;i++){
    if(columns[i].name==data.table){
      columns = columns[i].columns
      break
    }
  }
  delete columns.mark
  let ctable = quickSortForJSONWithBase64StringMark(getTableJSONData(data.databaseIndex,data.table,columns))
  for(let i=0;i<Object.keys(columns).length;i++){
    if(Object.keys(columns)[i]==data.colName){
      columns[Object.keys(columns)[i]] = data.colType
      break
    }
  }
  for(let i=0;i<ctable.length;i++){
    if(data.colType=='string'){
      ctable[i][data.colName] = ctable[i][data.colName].toString()
    }else{
      try {
        ctable[i][data.colName] = JSON.parse(ctable[i][data.colName])
      } catch {
        ctable[i][data.colName] = 0
      }
    }
  }
  ctable = changeDatabaseTableToSaveableFormat(ctable,columns)
  saveDataBaseTable(data,ctable)
  enableSaveTableSwitch(data)
}

async function tableAddCol(data){
  await prepareUserDatabase(data)
  for(let i=0;i<database.length;i++){
    if(database[i].name==data.database){
      data.databaseIndex = i
      break
    }
  }
  let columns = JSON.parse(await readFile("./db/" + data.user + "/" + data.database + "/tableConfig"))
  for(let i=0;i<columns.length;i++){
    if(columns[i].name==data.table){
      columns = columns[i].columns
      break
    }
  }
  delete columns.mark
  let ctable = quickSortForJSONWithBase64StringMark(getTableJSONData(data.databaseIndex,data.table,columns))
  for(let i=0;i<ctable.length;i++){
    ctable[i][data.colName] = (data.colType=='string')?'':0
  }
  columns[data.colName] = data.colType
  ctable = changeDatabaseTableToSaveableFormat(ctable,columns)
  saveDataBaseTable(data,ctable)
  enableSaveTableSwitch(data)
}

function jsonQuickSort(jsonArr, key) {
  if (jsonArr.length <= 1) {
    return jsonArr;
  }
  const pivotIndex = Math.floor(jsonArr.length / 2);
  const pivot = jsonArr[pivotIndex][key];
  const smallArr = [];
  const equalArr = [];
  const largeArr = [];
  for (let i = 0; i < jsonArr.length; i++) {
    const value = jsonArr[i][key];
    if (value < pivot) {
      smallArr.push(jsonArr[i]);
    } else if (value === pivot) {
      equalArr.push(jsonArr[i]);
    } else {
      largeArr.push(jsonArr[i]);
    }
  }
  let sortedArr;
  let order = 'asc'
  if (order === 'asc') {
    sortedArr = jsonQuickSort(smallArr, key, order)
      .concat(equalArr)
      .concat(jsonQuickSort(largeArr, key, order));
  } else {
    sortedArr = jsonQuickSort(largeArr, key, order)
      .concat(equalArr)
      .concat(jsonQuickSort(smallArr, key, order));
  }
  return sortedArr;
}

async function getTableColumns(data){
  let re = JSON.parse(await readFile("./db/" + data.config.user + "/" + data.config.database + "/tableConfig"))
  for(let i=0;i<re.length;i++){
    if(re[i].name==data.table){
      re = re[i].columns
      break
    }
  }
  return re
}

function enableSaveTableSwitch(data){
  database[data.databaseIndex].saveTableKeys[data.table] = true
}

async function saveTableToFile(databaseIndex,tableName,user,databaseName){
  let c = generateString(database[databaseIndex].tables[tableName])
  if((await checkPathExists("./db/" + user + "/" + databaseName + "/" + tableName))==false){
    await createFileWithContent("./db/" + user + "/" + databaseName + "/" + tableName,"")
  }
  await updateFileContent("./db/" + user + "/" + databaseName + "/" + tableName,c)
  database[databaseIndex].saveTableKeys[tableName] = false
}

function changeDatabaseTableToSaveableFormat(table,columns){
  let re = []
  for(let i=0;i<table.length;i++){
    let cre = [table[i].mark]
    for(let i1=0;i1<Object.keys(columns).length;i1++){
      cre.push(table[i][Object.keys(columns)[i1]].toString())
    }
    re.push(cre)
  }
  return re
}

function saveDataBaseTable(data,tableData){
  database[data.databaseIndex].tables[data.table] = tableData
}

function getTableJSONData(databaseIndex,table,columns){
  let ct = getTable(databaseIndex,table)
  ct = changeArrayToJSONByColumns(ct,columns)
  return ct
}

function getTable(databaseIndex,tableName){
  return database[databaseIndex].tables[tableName]
}

async function checkTokenAndGetDatabaseIndex(config){
  let re = await getUserPassword(config.user)
  if(re==false){
    return "Unauthorized."
  }
  if(getTokenContent(config.token).password!=re){
    return "Unauthorized."
  }
  await prepareUserDatabase(config)
  for(let i=0;i<database.length;i++){
    if(database[i].user==getTokenContent(config.token).user && database[i].name==getTokenContent(config.token).database){
      return i
    }
  }
}

async function getUserPassword(user){
  let re = JSON.parse(await readFile("./db/userConfig"))
  for(let i=0;i<re.length;i++){
    if(re[i].name==user){
      return re[i].password
    }
  }
  return false
}

function getTokenContent(token){
  let t = decodeString(token).split('-')
  return {
    user:t[0],
    password:t[1],
    database:t[2]
  }
}

function updateDatabaseStatus(config){
  for(let i=0;i<database.length;i++){
    if(database[i].user==config.user && database[i].name==config.database){
      database[i].time = new Date
      return
    }
  }
}

async function prepareUserDatabase(config){
    for(let i=0;i<database.length;i++){
        if(database[i].user==config.user && database[i].name==config.database){
            database[i].time = new Date
            return
        }
    }
    let newDatabase = {
        user:config.user,
        name:config.database,
        time:new Date,
        tables:{

        },
        saveTableKeys:{

        }
    }
    let re = await getDatabaseTables(config)
    for(let i=0;i<re.length;i++){
      if((await checkPathExists("./db/" + config.user + "/" + config.database + "/" + re[i]))==false){
        await createFileWithContent("./db/" + config.user + "/" + config.database + "/" + re[i],"")
      }
      let carr = loadArray(await readFile("./db/" + config.user + "/" + config.database + "/" + re[i]))
      newDatabase.tables[re[i]] = carr
      newDatabase.saveTableKeys[re[i]] = false
    }
    database.push(newDatabase)
}

async function getDatabaseTables(config){
  let re = JSON.parse(await readFile("./db/" + config.user + "/" + config.database + "/tableConfig"))
  let re0 = []
  for(let i=0;i<re.length;i++){
    re0.push(re[i].name)
  }
  return re0
}

async function checkDatabaseExists(config){
    let re = await getDirectoriesInFolder("./db/" + config.user + "/")
    for(let i=0;i<re.length;i++){
        if(re[i]==config.database){
            return "Success"
        }
    }
    return "The database does not exist"
}

async function checkUserExists(config){
    let re = await readFile("./db/userConfig")
    re = JSON.parse(re)
    for(let i=0;i<re.length;i++){
        if(re[i].name==config.user){
            if(re[i].password==config.password){
                return "Success"
            }else{
                return "Incorrect password"
            }
        }
    }
    return "User not found"
}

function createFolder(folderName) {
  return new Promise((resolve, reject) => {
    const folderPath = path.join(__dirname, folderName);

    fs.mkdir(folderPath, { recursive: true }, (err) => {
      if (err) {
        reject(err);
      } else {
        return resolve();
      }
    });
  });
}

function readFile(filePath) {
  return new Promise((resolve, reject) => {
    fs.readFile(filePath, 'utf8', (err, data) => {
      if (err) {
        reject(err)
      } else {
        return resolve(data)
      }
    })
  })
}

function createFileWithContent(filePath, content) {
  return new Promise((resolve, reject) => {
    fs.writeFile(filePath, content, { flag: 'wx' }, (err) => {
      if (err) {
        reject(err);
      } else {
        return resolve();
      }
    });
  });
}

function checkPathExists(fileOrFolderPath) {
  return new Promise((resolve) => {
    fs.access(fileOrFolderPath, fs.constants.F_OK, (err) => {
      if (err) {
        return resolve(false);
      } else {
        return resolve(true);
      }
    });
  });
}

function getDirectoriesInFolder(folderPath) {
  return new Promise((resolve, reject) => {
    fs.readdir(folderPath, (err, files) => {
      if (err) {
        reject(err)
        return
      }
      const directories = files.filter(file =>
        fs.statSync(path.join(folderPath, file)).isDirectory()
      )
      return resolve(directories)
    })
  })
}

function getFilesInFolder(folderPath) {
  return new Promise((resolve, reject) => {
    fs.readdir(folderPath, (err, files) => {
      if (err) {
        reject(err)
        return
      }
      const directories = files.filter(file =>
        fs.statSync(path.join(folderPath, file)).isFile()
      )
      return resolve(directories)
    })
  })
}

function checkData(data){
    try {
        data = decodeURIComponent(data)
        return JSON.parse(data)
    } catch (error) {
      console.log(error)
        return null
    }
}

function arrayDeleteElem(arr,index){
    let re = []
    for(let i=0;i<arr.length;i++){
        if(i==index){
            continue
        }
        re.push(arr[i])
    }
    return re
}

function encodeString(string){
    string = string.split('')
    let string1 = ''
    for(let i=0;i<string.length;i++){
        string1 += JSON.stringify(JSON.stringify(string[i].charCodeAt(0)).length) + JSON.stringify(string[i].charCodeAt(0))
    }
    string1 = string1.split('')
    for(let i=0;i<string1.length;i++){
        if(string1[i]=='0'){
            continue
        }
        let c = i
        let ss = string1[c]
        while (true) {
            let cd = isLetter(parseInt(ss))
            if(cd==2){
                break
            }
            if(cd==1){
                string1[c] = String.fromCharCode(parseInt(ss))
                for(let i1=i;i1<c;i1++){
                    string1[i1] = '-'
                }
                i = c
                break
            }
            c++
            if(c==string1.length){
                break
            }
            ss+=string1[c]
        }
    }
    for(let i=0;i<string1.length-1;i++){
        if(string1[i]=='2' && string1[i+1]=='2'){
            string1[i]='?'
            string1[i+1] = '-'
        }
    }
    let string2 = []
    for(let i=0;i<string1.length;i++){
        if(string1[i]=='-'){
            continue
        }
        string2.push(JSON.stringify((string1[i].charCodeAt(0) - string1[i].charCodeAt(0)%52)/52) + getNumberString(string1[i].charCodeAt(0)%52,2))
    }
    let string3 = ''
    for(let i = string2.length-1;i>=0;i--){
        string3 += string2[i]
    }
    string3 = string3.split('')
    let string4 = []
    for(let i=0;i<string3.length;i+=2){
        let ch = string3[i]
        if(i+1!=string3.length){
            ch+=string3[i+1]
        }
        if(parseInt(ch)<52 && ch.length==2){
            if(parseInt(ch)<26){
                string4.push(String.fromCharCode(parseInt(ch) + 65))
            }else{
                string4.push(String.fromCharCode(parseInt(ch) + 97 - 26))
            }
        }else{
            string4.push(ch)
        }
    }
    let string5 = ''
    for(let i=0;i<string4.length;i++){
        string5+=string4[i]
    }
    return string5
}

function getNumberString(n,x){
  n = JSON.stringify(n)
  for(let i = n.length;i<x;i++){
      n = '0' + n
  }
  return n
}

function isLetter(code){
  if(code>=65 && code<=90){
      return 1
  }
  if(code>=97 && code<=122){
      return 1
  }
  if(code>90 && code<97){
      return 2
  }
  if(code<65){
      return 0
  }
  return 2
}

function decodeString(string){
    string = string.split('')
    for(let i=0;i<string.length;i++){
        if(string[i].charCodeAt(0)>=48 && string[i].charCodeAt(0)<=56){
            continue
        }
        if(string[i].charCodeAt(0)<=90){
            string[i] = getNumberString(string[i].charCodeAt(0) - 65,2)
        }else{
            string[i] = getNumberString(string[i].charCodeAt(0) - 97 + 26,2)
        }
    }
    let string2 = ''
    for(let i=0;i<string.length;i++){
        if(string[i]=='-8'){
            string2+='9'
            continue
        }
        string2+=string[i]
    }
    string2 = string2.split('')
    let string3 = []
    for(let i=0;i<string2.length;i+=3){
        string3.push(getNumberString(parseInt(string2[i])*52 + parseInt(string2[i+1] + string2[i+2]),3))
    }
    let string4 = []
    for(let i=string3.length-1;i>=0;i--){
        string4.push(String.fromCharCode(parseInt(string3[i])))
    }
    let string5 = ''
    for(let i=0;i<string4.length;i++){
        if(string4[i]=='?'){
            string5+='22'
            continue
        }
        if((string4[i].charCodeAt(0)>=48 && string4[i].charCodeAt(0)<=57)==false){
            string5+=JSON.stringify(string4[i].charCodeAt(0))
            continue
        }
        string5+=string4[i]
    }
    string5 = string5.split('')
    let string6 = ''
    for(let i=0;i<string5.length;i++){
        let n = parseInt(string5[i])
        let s = ''
        for(let i1=0;i1<n;i1++){
            s+=string5[i+1+i1]
        }
        string6+=String.fromCharCode(parseInt(s))
        i+=n
    }
    return string6
}

function getToken(config){
    return encodeString(config.user + "-" + config.password + "-" + config.database)
}

function getCurrentTimeString(){
    let currentTime = new Date
    let re = ''
    re += getFullLengthNumber(currentTime.getFullYear(),4)
    re += getFullLengthNumber(currentTime.getMonth()+1,2)
    re += getFullLengthNumber(currentTime.getDate(),2)
    re += getFullLengthNumber(currentTime.getHours(),2)
    re += getFullLengthNumber(currentTime.getMinutes(),2)
    re += getFullLengthNumber(currentTime.getSeconds(),2)
    return re
}

async function waitSeconds(n){
  return new Promise((resolve, reject) => {
    setTimeout(()=>{
      return resolve()
    },n*1000)
  });
}

function generateString(data){
  if(data.length==0){
    return ""
  }
  let re = Object.values(data[0]).length.toString() + '.'
  for(let i=0;i<data.length;i++){
    for(let i1=0;i1<data[i].length;i1++){
      re += Object.values(data[i])[i1].length.toString() + "." + Object.values(data[i])[i1]
    }
  }
  return re
}

function loadArray(string){
  if(string==''){
    return []
  }
  let re = []
  let total = string.length
  let index = 0
  while (string.slice(index,index+1)!='.') {
    index += 1
  }
  let length = parseInt(string.slice(0,index))
  index += 1
  let cre = []
  while (index!=total) {
    let n = ''
    while (true) {
      let t = string.slice(index,index+1)
      if(t.charCodeAt(0)<48 || t.charCodeAt(0)>57){
        index+=1
        break
      }
      n += t
      index += 1
    }
    n = parseInt(n)
    let cs = string.slice(index,index+n)
    index += n
    cre.push(cs)
    if(cre.length==length){
      re.push(cre)
      cre = []
    }
  }
  return re
}

function changeArrayToJSONByColumns(arr,columns){
  let re = []
  for(let i=0;i<arr.length;i++){
    let cj = {
      mark:arr[i][0]
    }
    for(let i1=0;i1<Object.keys(columns).length;i1++){
      cj[Object.keys(columns)[i1]] = arr[i][i1+1]
      if(columns[Object.keys(columns)[i1]]=='number'){
          cj[Object.keys(columns)[i1]] = JSON.parse(arr[i][i1+1])
      }
    }
    re.push(cj)
  }
  return re
}

function checkAndModifyObject(data,columns){
  for(let i=0;i<data.length;i++){
    if(Object.values(data[i]).length>Object.values(columns).length){
      throw new FileDBError("The number of elements in row " + i.toString() + " exceeds the number of columns in the table.")
    }
    for(let i1=0;i1<Object.values(columns).length;i1++){
      if(Array.isArray(data[i])==false){
        data[i] = convertJSONToArray(data[i],columns)
      }
      if(i1>=data[i].length){
        switch (Object.values(columns)[i1]) {
          case 'string':
            data[i].push('')
            break
          case 'number':
            data[i].push(0)
            break
        }
      }else{
        if(typeof(data[i][i1])!=Object.values(columns)[i1]){
          throw new FileDBError("The data in column " + Object.keys(columns)[i1] + " should be of type \"" + Object.values(columns)[i1] + "\".")
        }
      }
    }
  }

  let re = []
  for(let i=0;i<data.length;i++){
    let cj = {}
    for(let i1=0;i1<Object.keys(columns).length;i1++){
      cj[Object.keys(columns)[i1]] = data[i][i1]
    }
    re.push(cj)
  }
  return re
}

function convertJSONToArray(json,columns){
  let re = []
  for(let i=0;i<Object.keys(columns).length;i++){
    re.push(json[Object.keys(columns)[i]])
  }
  return re
}

function updateFileContent(filePath, content) {
  return new Promise((resolve, reject) => {
    fs.unlink(filePath, (error) => {
      if (error && error.code !== 'ENOENT') {
        reject(error);
      } else {
        fs.writeFile(filePath, content, { flag: 'w+' }, (error) => {
          if (error) {
            reject(error);
          } else {
            resolve();
          }
        });
      }
    });
  });
}

class Base64String{
  constructor(n=0){
      this.data = "0"
      this.setByBase10(n)
  }

  _base64StringArrAdd0AtBegin(stringArr){
      let re = ['0']
      for(let i=0;i<stringArr.length;i++){
          re.push(stringArr[i])
      }
      return re
  }

  _base64StringCompare(baseString1,baseString2){
  
      if(string1.length<string2.length){
          return -1
      }
      if(string1.length>string2.length){
          return 1
      }
      for(let i=0;i<string1.length;i++){
          if(string1.slice(i,i+1).charCodeAt(0)<string2.slice(i,i+1).charCodeAt(0)){
              return -1
          }
          if(string1.slice(i,i+1).charCodeAt(0)>string2.slice(i,i+1).charCodeAt(0)){
              return 1
          }
      }
      return 0
  }

  _base64StringAdd1(string){
      string = string.split('')
      let flag = true
      let index = string.length - 1
      while (flag==true) {
          if(string[index]=='β'){
              string[index]='0'
              index-=1
              if(index==-1){
                  index=0
                  string = this._base64StringArrAdd0AtBegin(string)
              }
          }else{
              string[index] = String.fromCharCode(string[index].charCodeAt(0) + 1)
              if(string[index] == String.fromCharCode(58)){
                  string[index] = 'A'
                  flag = false
              }
              if(string[index] == String.fromCharCode(91)){
                  string[index] = 'a'
                  flag = false
              }
              if(string[index] == String.fromCharCode(123)){
                  string[index] = 'α'
                  flag = false
              }
              flag = false
          }
      }
      let re = ''
      for(let i=0;i<string.length;i++){
          re+=string[i]
      }
      return re
  }

  add1(){
      let string = this.data
      string = string.split('')
      let flag = true
      let index = string.length - 1
      while (flag==true) {
          if(string[index]=='β'){
              string[index]='0'
              index-=1
              if(index==-1){
                  index=0
                  string = this._base64StringArrAdd0AtBegin(string)
              }
          }else{
              string[index] = String.fromCharCode(string[index].charCodeAt(0) + 1)
              if(string[index] == String.fromCharCode(58)){
                  string[index] = 'A'
                  flag = false
              }
              if(string[index] == String.fromCharCode(91)){
                  string[index] = 'a'
                  flag = false
              }
              if(string[index] == String.fromCharCode(123)){
                  string[index] = 'α'
                  flag = false
              }
              flag = false
          }
      }
      let re = ''
      for(let i=0;i<string.length;i++){
          re+=string[i]
      }
      this.data = re
  }

  sub1(){
      let string = this.data
      let re = "0"
      let last = ""
      while (this._base64StringCompare(re,string)) {
          last = re
          re = this._base64StringAdd1(re)
      }
      this.data = re
  }

  setByBase10(n){
      this.data = "0"
      for(let i=0;i<n;i++){
          this.add1()
      }
  }

  setByBase64(string){
      this.data = string
  }

  get(){
      return this.data
  }

  getBase10(){
      let re = 0
      let c = "0"
      while(c!=this.data){
          re+=1
          c = this._base64StringAdd1(c)
      }
      return re
  }
}

function quickSortForJSONWithBase64StringMark(arr){
  arr = quickSort(arr,jsonWithBase64StringMarkCompare)
  return arr
}

function jsonWithBase64StringMarkCompare(json1,json2){
  return base64StringCompare(json1.mark,json2.mark)
}

function base64StringCompare(string1,string2){
  if(string1.length<string2.length){
      return -1
  }
  if(string1.length>string2.length){
      return 1
  }
  for(let i=0;i<string1.length;i++){
      if(string1.slice(i,i+1).charCodeAt(0)<string2.slice(i,i+1).charCodeAt(0)){
          return -1
      }
      if(string1.slice(i,i+1).charCodeAt(0)>string2.slice(i,i+1).charCodeAt(0)){
          return 1
      }
  }
  return 0
}

function quickSort(arr, compareFunction) {
if (arr.length <= 1) {
  return arr;
}

const pivotIndex = Math.floor(arr.length / 2);
const pivot = arr[pivotIndex];

const smaller = [];
const equal = [];
const larger = [];

for (let i = 0; i < arr.length; i++) {
  const result = compareFunction(arr[i], pivot);
  if (result < 0) {
    smaller.push(arr[i]);
  } else if (result === 0) {
    equal.push(arr[i]);
  } else {
    larger.push(arr[i]);
  }
}

return quickSort(smaller, compareFunction).concat(equal, quickSort(larger, compareFunction));
}